db/state, execution: reconcile caches when immutable files become visible - #23047
Draft
yperbasis wants to merge 1 commit into
Draft
db/state, execution: reconcile caches when immutable files become visible#23047yperbasis wants to merge 1 commit into
yperbasis wants to merge 1 commit into
Conversation
pull Bot
pushed a commit
to Dustin4444/erigon
that referenced
this pull request
Aug 13, 2026
…igontech#23005) Fixes erigontech#22463. ## Summary `StateCache` stores latest committed state. Unwind already made resident dead-fork entries stale, but readers could add those values again from an old or transient view: a transaction could survive or first bind during unwind, staged unwind rows still existed in the backing database, and read-ahead could fill concurrently. This PR closes those windows by binding fill authority to both the durable `PlainStateVersion` and the lifetime of the original `ReadView`. Reads constrained by a staged unwind cannot fill, cache changes are published only after the database commit, and read-ahead cannot cross the unwind transition. Snapshot and immutable-file publication are a separate coherence boundary. erigontech#23028 still requires erigontech#23047 or an equivalent publication hook and is not addressed here. Bounded speculative-unwind fills in the separate commitment `BranchCache` pre-exist this PR and are tracked in erigontech#23253. ## Review guide Suggested order: 1. `execution/cache/view.go` and `state_cache.go`: fill admission and publication. 2. `db/state/execctx/domain_shared.go`: transaction identity, bounded reads, and commit/unwind integration. 3. `db/kv/membatchwithdb/memory_mutation.go` and `db/state/temporal_mem_batch.go`: `PlainStateVersion` ownership and monotonicity. 4. `execution/exec/blocks_read_ahead.go` and `execution/execmodule`: read-ahead exclusion and lifecycle. Focused regression tests sit beside each area. ## Correctness invariants | Marker | Protects | | --- | --- | | `PlainStateVersion` | The durable state visible to a transaction | | `readViewEpoch` | Whether a `ReadView` predates the latest unwind or state discontinuity | | Per-cache entry epoch and unwind floor | Whether a stored value belongs to the retained fork | Once the cache has a durable state version, an admission-gated state fill is accepted only if the view has the published state version and current epoch, publication is not in progress, its exact domain frontier is not behind the cache, and the read has no staged-unwind step bound. Content-addressed code-size fills do not need these state-view checks. An ineligible view may still read cache hits; only its fill authority is revoked. `WithFrontier` preserves the original epoch, so rebinding cannot renew an old view. Stored entries remain O(1) to invalidate and are discarded lazily. The three markers stay separate because durable state, reader, and stored-entry lifetimes change at different boundaries. ## Commit and unwind flow 1. Staging an unwind revokes existing views, invalidates stored entries, and records the lowest staged boundary. Bounded reads cannot fill. 2. Flush advances `PlainStateVersion` exactly once with the domain writes and collects cache updates without publishing them. 3. The database transaction commits. 4. Cache publication applies the complete batch. It repeats unwind invalidation at the durable boundary, rejects delayed or out-of-order versions, preserves entries after a continuous forward commit, and clears them when continuity is unknown. During publication, reads and view binding remain available, but fills are disabled. A view bound during publication remains fill-inert until explicitly rebound. `MemoryMutation` resolves untouched sequences from its backing transaction and flushes only changed sequence keys, so it cannot replay an older state version. Pre-commit notifications receive the projected version explicitly rather than deriving it from overlay sequence writes. Notification ordering itself is unchanged and remains tracked in erigontech#23240. One semaphore permit covers read-ahead warmup and unwind exclusion. A warmup acquires it without blocking, so work requested while another warmup or an unwind owns or waits for the permit is skipped rather than queued. Unwind callers acquire it with their context and abort before staging if cancellation wins. `updateForkChoice` and `SetHead` hold the permit through unwind and publication; `ValidateChain` acquires it only when it stages an unwind. Every FCU currently excludes warmup, including FCUs that do not unwind; narrowing that scope is tracked in erigontech#23003. ## Performance - The cache-hit path is unchanged. - `View(nil)` adds one atomic load. Binding a fill-enabled `ReadView` also takes `admissionMu.RLock` to check publication and state-version eligibility; getters retain that view instead of paying the binding cost per key. - Normal getters reuse the `SharedDomains` transaction's memoized state version. A different transaction resolves its version at initial binding. If that resolution temporarily fails, later cache misses retry it; each retry is local to that miss. - Unwind invalidation remains O(1), with no cache scan or diff replay. - Each accepted warmup performs one uncontended semaphore acquisition. Rejected warmups do not start a goroutine, and the gate is never touched per key. ## Validation Regression tests cover old and newly bound views across every unwind phase, bounded state and code-hash reads, delayed publications, memory-overlay state versions, read-ahead exclusion and cancellation, and valid forward fills. --------- Co-authored-by: Alexey Sharov <askalexsharov@gmail.com>
yperbasis
force-pushed
the
yperbasis/statecache-publication-boundary
branch
from
August 17, 2026 14:20
0566925 to
8c05677
Compare
yperbasis
force-pushed
the
yperbasis/statecache-publication-boundary
branch
from
August 17, 2026 14:26
8c05677 to
a090b25
Compare
yperbasis
marked this pull request as ready for review
August 17, 2026 14:27
yperbasis
requested review from
AskAlexSharov,
awskii,
mh0lt,
sudeepdino008 and
taratorio
as code owners
August 17, 2026 14:27
yperbasis
marked this pull request as draft
August 17, 2026 14:32
yperbasis
marked this pull request as ready for review
August 17, 2026 14:33
yperbasis
marked this pull request as draft
August 17, 2026 14:34
| var branchView commitment.BranchCacheView | ||
| if domain == kv.CommitmentDomain && sd.branchCache != nil { | ||
| if cv, cStepU64, ok := sd.branchCache.Get(k); ok { | ||
| branchView = sd.branchCache.View() |
Collaborator
There was a problem hiding this comment.
this looks broken. View must be long-living - to provide consistent data view across all Cache uses.
| } | ||
|
|
||
| // BindStateCache reconciles a cache with current and future file visibility. | ||
| func (a *Aggregator) BindStateCache(stateCache *cache.StateCache) { |
Collaborator
There was a problem hiding this comment.
reconciles- word has no meaning for me.- if we have
stateCache.View()which passed as a parameter to many funcs - then it's unclear for me why need bindAggandStateCache. WhatBinddoes?a.boundStateCache != stateCache - I still don't understand - why high-level cache - must look inside files management. It's abstractions leak. We already have mechanic which guarantee consistent data view:
txobject. All other "global atomic fields which need to invalidate sometime" - is anti-pattern.
|
|
||
| coh coherence.Gen | ||
| // appliedEnd gates read-sourced writes. coveredEnd also tracks quiet commits | ||
| // so locally built files do not churn the cache. |
Collaborator
There was a problem hiding this comment.
how built files can churn cache?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #23028.
Problem
Immutable state files can become visible without passing through the commit paths that update
StateCacheand the commitmentBranchCache. Cache entries loaded against the previous file generation can therefore survive the transition or be reinserted by an older read, causing execution to observe state that the newly visible files replaced.Solution
Treat
Aggregator.recalcVisibleFilesas the cache-coherence boundary. Before publishing a new visible-file bundle, the aggregator now reconciles both the boundStateCacheand its commitmentBranchCache. Binding also reconciles files that are already visible, and apply-only state caches participate because file publication can bypass authoritative applies even when reader fills are disabled.StateCachekeeps separate per-domain mutation, commit-coverage, and file frontiers. Files beyond observed commit coverage clear the cache, advance fill admission, and revoke older read views. Locally covered files are a no-op, including quiet domains, and the immutable-file floor survives state-version initialization.BranchCachenow distinguishes authoritative writes, quiet commit coverage, read fills, and adaptive preloads. Read-sourced entries carry both a state frontier and a file-visibility generation, so publication, visibility lowering, clear, or unwind cannot be followed by a delayed stale insertion. Authoritative writes behind an already-published frontier are rejected as well.Performance
Normal file recalculations do not copy cache contents or retain commit write sets. Identical commitment visibility returns before taking cache locks, and locally built file ranges do not clear either cache. Reconciliation work runs only when the aggregator recalculates visible files; the hot state-cache read path is unchanged.
Validation
make test-shortexecution/cache,execution/commitment,db/state,db/state/execctx, andexecution/execmodulego veton the affected packagesmake erigon integration